import 'dart:math' as math;
import 'dart:typed_data';

import 'package:vector_math/vector_math.dart';
import 'package:vector_math_test/src/test_utils.dart';

import '../common/test_page.dart';

class Vector4TestPage extends TestPage{
  Vector4TestPage(super.title) {
    group('Vector4', () {
      test('Vector4().length, Vector4().length2, Vector4().normalize()', testVector4Length);
      test('Vector4().length(double value)', testVector4SetLength);
      test('Vector4().negate()', testVector4Negate);
      test('Vector4(), Vector4.all(double value), Vector4.random([math.Random? rng])', testVector4Constructor);
      test('Vector4().add(Vector4 arg), Vector4().addScaled(Vector4 arg, double factor)', testVector4Add);
      test('Vector4().min(Vector4 a, Vector4 b, Vector4 result), Vector4().max(Vector4 a, Vector4 b, Vector4 result)', testVector4MinMax);
      test('Vector4.mix(Vector4 min, Vector4 max, double a, Vector4 result)', testVector4Mix);
      test('Vector4.distanceTo(Vector4 arg)', testVector4DistanceTo);
      test('Vector4.distanceToSquared(Vector4 arg)', testVector4DistanceToSquared);
      test('Vector4.fromFloat32List(this._v4storage)', testVector4InstacinfFromFloat32List);
      test('Vector4.fromBuffer(ByteBuffer buffer, int offset)', testVector4InstacingFromByteBuffer);
      test('Vector4()..clamp(Vector4 min, Vector4 max)', testVector4Clamp);
      test('Vector4()..clampScalar(double min, double max)', testVector4ClampScalar);
      test('Vector4()..floor()', testVector4Floor);
      test('Vector4()..ceil()', testVector4Ceil);
      test('Vector4()..round()', testVector4Round);
      test('Vector4()..roundToZero()', testVector4RoundToZero);
    });
  }

  void testVector4InstacinfFromFloat32List() {
    final float32List = Float32List.fromList([1.0, 2.0, 3.0, 4.0]);
    final input = Vector4.fromFloat32List(float32List);

    expect(input.x, 1.0);
    expect(input.y, 2.0);
    expect(input.z, 3.0);
    expect(input.w, 4.0);
  }

  void testVector4InstacingFromByteBuffer() {
    final float32List = Float32List.fromList([1.0, 2.0, 3.0, 4.0, 5.0]);
    final buffer = float32List.buffer;
    final zeroOffset = Vector4.fromBuffer(buffer, 0);
    final offsetVector = Vector4.fromBuffer(buffer, Float32List.bytesPerElement);

    expect(zeroOffset.x, 1.0);
    expect(zeroOffset.y, 2.0);
    expect(zeroOffset.z, 3.0);
    expect(zeroOffset.w, 4.0);

    expect(offsetVector.x, 2.0);
    expect(offsetVector.y, 3.0);
    expect(offsetVector.z, 4.0);
    expect(offsetVector.w, 5.0);
  }

  void testVector4Add() {
    final a = Vector4(5.0, 7.0, 3.0, 10.0);
    final b = Vector4(3.0, 8.0, 2.0, 2.0);

    a.add(b);
    expect(a.x, 8.0);
    expect(a.y, 15.0);
    expect(a.z, 5.0);
    expect(a.w, 12.0);

    b.addScaled(a, 0.5);
    expect(b.x, 7.0);
    expect(b.y, 15.5);
    expect(b.z, 4.5);
    expect(b.w, 8.0);
  }

  void testVector4MinMax() {
    final a = Vector4(5.0, 7.0, -3.0, 10.0);
    final b = Vector4(3.0, 8.0, 2.0, 2.0);

    final result = Vector4.zero();

    Vector4.min(a, b, result);
    expect(result.x, 3.0);
    expect(result.y, 7.0);
    expect(result.z, -3.0);
    expect(result.w, 2.0);

    Vector4.max(a, b, result);
    expect(result.x, 5.0);
    expect(result.y, 8.0);
    expect(result.z, 2.0);
    expect(result.w, 10.0);
  }

  void testVector4Mix() {
    final a = Vector4(5.0, 7.0, 3.0, 10.0);
    final b = Vector4(3.0, 8.0, 2.0, 2.0);

    final result = Vector4.zero();

    Vector4.mix(a, b, 0.5, result);
    expect(result.x, 4.0);
    expect(result.y, 7.5);
    expect(result.z, 2.5);
    expect(result.w, 6.0);

    Vector4.mix(a, b, 0.0, result);
    expect(result.x, 5.0);
    expect(result.y, 7.0);
    expect(result.z, 3.0);
    expect(result.w, 10.0);

    Vector4.mix(a, b, 1.0, result);
    expect(result.x, 3.0);
    expect(result.y, 8.0);
    expect(result.z, 2.0);
    expect(result.w, 2.0);
  }

  void testVector4Constructor() {
    final v1 = Vector4(2.0, 4.0, -1.5, 10.0);
    expect(v1.x, 2.0);
    expect(v1.y, 4.0);
    expect(v1.z, -1.5);
    expect(v1.w, 10.0);

    final v2 = Vector4.all(2.0);
    expect(v2.x, 2.0);
    expect(v2.y, 2.0);
    expect(v2.z, 2.0);
    expect(v2.w, 2.0);

    final v3 = Vector4.random(math.Random());
    expect(v3.x, '');
    expect(v3.y, '');
    expect(v3.z, '');
    expect(v3.w, '');
  }

  void testVector4Length() {
    final a = Vector4(5.0, 7.0, 3.0, 10.0);

    relativeTest(a.length, 13.5277);
    relativeTest(a.length2, 183.0);

    relativeTest(a.normalize(), 13.5277);
    relativeTest(a.x, 0.3696);
    relativeTest(a.y, 0.5174);
    relativeTest(a.z, 0.2217);
    relativeTest(a.w, 0.7392);
  }

  void testVector4SetLength() {
    final v0 = Vector4(1.0, 2.0, 1.0, 1.0);
    final v1 = Vector4(3.0, -2.0, 2.0, 1.0);
    final v2 = Vector4(-1.0, 2.0, -2.0, -3.0);
    final v3 = Vector4(1.0, 0.0, 0.0, 0.0);

    v0.length = 0.0;
    relativeTest(v0, Vector4.zero());
    relativeTest(v0.length, 0.0);

    v1.length = 2.0;
    relativeTest(
        v1,
        Vector4(1.4142135381698608, -0.9428090453147888, 0.9428090453147888,
            0.4714045226573944));
    relativeTest(v1.length, 2.0);

    v2.length = 0.5;
    relativeTest(
        v2,
        Vector4(-0.1178511306643486, 0.2357022613286972, -0.2357022613286972,
            -0.3535533845424652));
    relativeTest(v2.length, 0.5);

    v3.length = -1.0;
    relativeTest(v3, Vector4(-1.0, 0.0, 0.0, 0.0));
    relativeTest(v3.length, 1.0);
  }

  void testVector4Negate() {
    final vec3 = Vector4(1.0, 2.0, 3.0, 4.0);
    vec3.negate();
    expect(vec3.x, -1.0);
    expect(vec3.y, -2.0);
    expect(vec3.z, -3.0);
    expect(vec3.w, -4.0);
  }

  void testVector4DistanceTo() {
    final a = Vector4(1.0, 1.0, 1.0, 0.0);
    final b = Vector4(1.0, 3.0, 1.0, 0.0);
    final c = Vector4(1.0, 1.0, -1.0, 0.0);

    expect(a.distanceTo(b), 2.0);
    expect(a.distanceTo(c), 2.0);
  }

  void testVector4DistanceToSquared() {
    final a = Vector4(1.0, 1.0, 1.0, 0.0);
    final b = Vector4(1.0, 3.0, 1.0, 0.0);
    final c = Vector4(1.0, 1.0, -1.0, 0.0);

    expect(a.distanceToSquared(b), 4.0);
    expect(a.distanceToSquared(c), 4.0);
  }

  void testVector4Clamp() {
    final x = 2.0, y = 3.0, z = 4.0, w = 5.0;
    final v0 = Vector4(x, y, z, w);
    final v1 = Vector4(-x, -y, -z, -w);
    final v2 = Vector4(-2.0 * x, 2.0 * y, -2.0 * z, 2.0 * w)..clamp(v1, v0);

    expect(v2.storage, '');
  }

  void testVector4ClampScalar() {
    final x = 2.0;
    final v0 = Vector4(-2.0 * x, 2.0 * x, -2.0 * x, 2.0 * x)..clampScalar(-x, x);

    expect(v0.storage, '');
  }

  void testVector4Floor() {
    final v0 = Vector4(-0.1, 0.1, -0.1, 0.1)..floor();
    final v1 = Vector4(-0.5, 0.5, -0.5, 0.5)..floor();
    final v2 = Vector4(-0.9, 0.9, -0.5, 0.9)..floor();

    expect(v0.storage, '');
    expect(v1.storage, '');
    expect(v2.storage, '');
  }

  void testVector4Ceil() {
    final v0 = Vector4(-0.1, 0.1, -0.1, 0.1)..ceil();
    final v1 = Vector4(-0.5, 0.5, -0.5, 0.5)..ceil();
    final v2 = Vector4(-0.9, 0.9, -0.9, 0.9)..ceil();

    expect(v0.storage, '');
    expect(v1.storage, '');
    expect(v2.storage, '');
  }

  void testVector4Round() {
    final v0 = Vector4(-0.1, 0.1, -0.1, 0.1)..round();
    final v1 = Vector4(-0.5, 0.5, -0.5, 0.5)..round();
    final v2 = Vector4(-0.9, 0.9, -0.9, 0.9)..round();

    expect(v0.storage, '');
    expect(v1.storage, '');
    expect(v2.storage, '');
  }

  void testVector4RoundToZero() {
    final v0 = Vector4(-0.1, 0.1, -0.1, 0.1)..roundToZero();
    final v1 = Vector4(-0.5, 0.5, -0.5, 0.5)..roundToZero();
    final v2 = Vector4(-0.9, 0.9, -0.9, 0.9)..roundToZero();
    final v3 = Vector4(-1.1, 1.1, -1.1, 1.1)..roundToZero();
    final v4 = Vector4(-1.5, 1.5, -1.5, 1.5)..roundToZero();
    final v5 = Vector4(-1.9, 1.9, -1.9, 1.9)..roundToZero();

    expect(v0.storage, '');
    expect(v1.storage, '');
    expect(v2.storage, '');
    expect(v3.storage, '');
    expect(v4.storage, '');
    expect(v5.storage, '');
  }

}